/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/

/**
 * @file
 * @brief Implement the IR-codes dumper.
 *
 * @author  WXQ#XDU
 * @date    2025.01
 */

#include <string.h>

#include "../include/amcc_log.h"
#include "../include/amcc_error.h"
#include "../include/amcc_time.h"
#include "../include/amcc_string.h"

#include "inside/_emit_inner.h"

// Structure of IR-codes dumper
typedef struct ircode_dumper {
    int  funcCounters;
    int  codeCounters;
    const char * tempVarPrefix;
    FILE * fp;

    t_func_code_pair *  currentFuncCodes;
} t_dumper, * t_dumper_ptr ;


static int dumpOneFunc(t_list_app_value app_data, void* user_data);
static int dumpIRcode(t_list_app_value app_data, void* user_data);

#define COMMENT_HEAD  "# "


// API of IR-codes dumper
void ircodes_dump(t_program_codes programCodes) {
    FILE * fp = fopen(amcc_get_irCodeFilePath(), "wt");
    if (NULL == fp) {
        ERROR_REPORT_FILEOP(EC_WRITEFILE, amcc_get_irCodeFilePath());
        return;
    }

    t_dumper  dumper;
    dumper.fp = fp;
    dumper.funcCounters = 0;
    dumper.codeCounters = 0;
    dumper.tempVarPrefix = amcc_get_tempvar_prefix();


    log_info("====> DUMPing IR-codes into file [%s]", amcc_get_irCodeFilePath());

    // File header
    const char * nowTimeTxt = amcc_get_timestring(NULL);
    fprintf(fp, "/*\n"
                "   Generator: [" AMCC_PRJ_NAME "]\n"
                "  Local-Time: [%s]\n"
                "  Input-File: [%s]\n"
                "\n"
                "        ======  N O T I C E  ======\n"
                "  In current version of AMCC,\n"
                "    - No type conversion considered: itr and rti;\n"
                "    - No code optimization considered.\n"
                "*/\n\n"
                , nowTimeTxt, amcc_get_inputFilePath());

    // File body: IR-codes of all functions, translation-unit(global)
    if ( list_is_empty( programCodes ) ) {
        log_warn( "Hi, NO dump an empty IR-codes  '`v_v`'" );
        fprintf(fp, "\n\n" COMMENT_HEAD "No any codes.\n\n");
    } else {
        list_foreach(programCodes, dumpOneFunc, &dumper);
    }

    // File tail
    fprintf(fp, "\n" COMMENT_HEAD
            "---------------------------------------------------------\n" );
    fprintf(fp, COMMENT_HEAD "total %d functions, %d three-address-codes\n",
            dumper.funcCounters - 1, // excludes translation-unit
            dumper.codeCounters);
    fprintf(fp, "\n" COMMENT_HEAD "End-Of-File\n" );

    fclose(fp);

    log_info( "====> DUMPed  IR-codes into file [%s],"
              " total %d functions, %d three-address-codes" ,
              amcc_get_irCodeFilePath(),
              dumper.funcCounters - 1,   dumper.codeCounters);
}


#define  UNIT_NAME1(nm)  (nm != NULL ? "func" : ".global")
#define  UNIT_NAME2(nm)  (nm != NULL ? nm : "")

// Dump IR-codes of a function or the translation-unit
static int dumpOneFunc(t_list_app_value app_data, void* user_data) {
    t_func_code_pair * funcCodePair = (t_func_code_pair *)app_data;
    t_dumper_ptr   dumperPtr = (t_dumper_ptr)user_data;
    dumperPtr->funcCounters += 1;
    dumperPtr->codeCounters += list_size(funcCodePair->codes);

    t_position   pos1 = tree_get_startPosition(funcCodePair->declNode);
    t_position   pos2 = tree_get_stopPosition(funcCodePair->declNode);
    const char * unitName = NULL;
    bool isGlobal = true;

    // this is a function on true, or the translation-unit on false
    if (tree_is_markedByNT(funcCodePair->declNode, NT_funcDeclaration)) {
        t_tree_ptr funcNameNode = tree_get_child(funcCodePair->declNode, 1);
        unitName = tree_get_markText(funcNameNode);
        isGlobal = false;
    }

    if ( isGlobal ) {
        fprintf(dumperPtr->fp,
            "\n" COMMENT_HEAD "NOTICE:"
            " For global scope, only emit codes to"
            " initialize global variables\n"          );
    }
    fprintf(dumperPtr->fp,
            "\n%s %s :      " COMMENT_HEAD
            "position is (%d,%d -- %d,%d)\n\n",
            UNIT_NAME1(unitName), UNIT_NAME2(unitName),
            pos1.line, pos1.column, pos2.line, pos2.column);

    dumperPtr->currentFuncCodes = funcCodePair;
    list_foreach(funcCodePair->codes, dumpIRcode, dumperPtr);
    dumperPtr->currentFuncCodes = NULL;

    fprintf(dumperPtr->fp,
            "\nend %s %s    " COMMENT_HEAD
            "position is (%d,%d -- %d,%d)\n\n",
            UNIT_NAME1(unitName), UNIT_NAME2(unitName),
            pos1.line, pos1.column, pos2.line, pos2.column);
    return 0;
}


typedef struct  op_pattern {
    EnumOpCode   opcode;
    const char * opcodeText;
    const char * pattern;
} t_op_pattern;


// All pairs of (op-code, pattern) for expected output text.
// Elements are in ordered by op-code.
static t_op_pattern  opPatterns[] = {
    #define IRCODE_DEF(op, pattern, others...)  {op, #op, pattern}
    #include "../meta/_ir_opcode.inc"
    #undef IRCODE_DEF
};


// callback comparator for bsearch()
static int  cmp_opcode__(const void * a, const void *b) {
    t_op_pattern * pa = (t_op_pattern *)a;
    t_op_pattern * pb = (t_op_pattern *)b;
    if (pa->opcode > pb->opcode)
        return 1;
    if (pa->opcode < pb->opcode)
        return -1;
    return 0;
}

static t_op_pattern * findOpPattern__(EnumOpCode opcode) {
    t_op_pattern  key;      key.opcode = opcode;
    size_t  numOfElements = sizeof(opPatterns) / sizeof(opPatterns[0]);
    t_op_pattern * opPattern = bsearch(&key, opPatterns, numOfElements,
                         sizeof(opPatterns[0]), cmp_opcode__);
    return opPattern;
}

const char * dumpIRcode_codeName(EnumOpCode opcode) {
    t_op_pattern * opPattern = findOpPattern__(opcode);
    if (opPattern == NULL)
        return NULL;
    return opPattern->opcodeText;
}

// Dump one IR-code according to a pattern
static void dumpIRcode_impl(t_ircode_ptr codePtr,
                            t_dumper_ptr dumperPtr,
                            const char * pattern)
{
    char buffer[200] = {0}; // big enough ?
    sprintf(buffer, "%6u  :    ", codePtr->id);
    char * dest = buffer + strlen(buffer);

    bool isGoto = OP_GOTO_MIN_ < codePtr->opcode && codePtr->opcode < OP_GOTO_MAX_;
    t_token_ptr  tokenPtr;
    const char * p = pattern;
    for (EnumAddrIndex flag ; *p != '\0' ; ) {
        flag = AI_BAD;
        if ( *p == '%' ) {  // may be a placeholder
            switch( *(p+1) ) {
                case '1' : flag = AI_ARG1; break;
                case '2' : flag = AI_ARG2; break;
                case 'r' : case 'R' :
                           flag = AI_RES;  break;
                default  : break;  // not a pre-defined placeholder
            }
        }

        if ( flag == AI_BAD ) { *dest++ = *p++; } // just copy it
        else {          // convert one address to string
            p += 2;
            t_ir_address * address = (codePtr->address[flag]);
            if ( NULL == address ) {
                sprintf(dest, "<NULL>");
            }
            else  switch(address->addrKind) {
                case AK_IRCODE:   // IR-code's id
                    if ( isGoto && 0 == address->value.targetCodeId )
                        strcpy(dest, "-"); // unKnown target
                    else
                        sprintf(dest, "%u", address->value.targetCodeId);
                    break;
                case AK_TEMPVAR:  // compiler-generated temporary variable
                    sprintf(dest, "%s%u",
                                dumperPtr->tempVarPrefix,
                                address->value.tempvarId);
                    break;
                case AK_TOKEN:  // a variable or literal in source-program
                    tokenPtr = tree_get_token(address->value.treeNode);
                    sprintf(dest, "%s", token_get_text(tokenPtr));
                    break;
                default:
                    ERROR_REPORT_BUG("invalid address kind %d for instruction %d, %s",
                                     address->addrKind, codePtr->opcode,
                                     __FUNCTION__ );
                    break;
            }
            dest += strlen(dest);
        }
    }

    fprintf(dumperPtr->fp, "%s", buffer);
    if ( NULL != codePtr->comment.text ) {
        // To align the comments, compute the length of padding blanks
        int  len = (int) (strlen(buffer));
        int  expectedCodeLen = 40;
        if ( len >= expectedCodeLen ) len = 4;
        else len = expectedCodeLen - len;
        fprintf(dumperPtr->fp,   "%.*s" COMMENT_HEAD "%s",
                len, amcc_long_spacestring(), codePtr->comment.text );
    }
    fprintf(dumperPtr->fp, "\n");
}


// Dump one IR-code
static int dumpIRcode(t_list_app_value app_data, void* user_data){

    t_ircode_ptr   codePtr = (t_ircode_ptr)app_data;
    t_dumper_ptr dumperPtr = (t_dumper_ptr)user_data;

    char tmpPattern[100];
    const char * pattern = NULL;
    if ( OP_UNKNOWN__  !=  codePtr->opcode ) {
        t_op_pattern * opPattern = findOpPattern__(codePtr->opcode);
        if (NULL != opPattern)
            pattern = opPattern->pattern;
    }

    if (NULL == pattern) {
        const char * fmt = "!!! NotDefinedOpCode: ( ?op=%d? , %%1 , %%2 , %%R )";
        snprintf(tmpPattern, sizeof(tmpPattern)-1,fmt, codePtr->opcode);
        pattern =  tmpPattern;
    }

    dumpIRcode_impl(codePtr, dumperPtr, pattern);
    return 0;
}
